<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.

Use of this source code is governed by an Apache 2.0 License.
See the COPYING file for details.
-->
<head>
<title>Closure Unit Tests - goog.testing.LooseMock</title>
<script src="../base.js"></script>
<script>
  goog.require('goog.testing.LooseMock');
  goog.require('goog.testing.PropertyReplacer');
  goog.require('goog.testing.jsunit');
  goog.require('goog.testing.mockmatchers');
</script>
</head>
<body>
<script>
  // The object that we will be mocking
  var RealObject = function() {
  };

  RealObject.prototype.a = function() {
    fail('real object should never be called');
  };

  RealObject.prototype.b = function() {
    fail('real object should never be called');
  };

  var mock;

  var stubs;

  function setUp() {
    var obj = new RealObject();
    mock = new goog.testing.LooseMock(obj);
    stubs = new goog.testing.PropertyReplacer();
  }

  function tearDown() {
    stubs.reset();
  }

  /*
   * Calling this method evades the logTestFailure in
   * goog.testing.Mock.prototype.$recordAndThrow, so it doesn't look like the
   * test failed.
   */
  function silenceFailureLogging() {
    if (goog.global['G_testRunner']) {
      stubs.set(goog.global['G_testRunner'],
          'logTestFailure', goog.nullFunction);
    }
  }

  function unsilenceFailureLogging() {
    stubs.reset();
  }

  /**
   * Version of assertThrows that doesn't log the exception generated by the
   * mocks under test.
   * TODO: would be nice to check that a particular substring is in the thrown
   * message so we know it's not something dumb like a syntax error
   */
  function assertThrowsQuiet(var_args) {
    silenceFailureLogging();
    assertThrows.apply(null, arguments);
    unsilenceFailureLogging();
  }

  // Most of the basic functionality is tested in strictmock_test; these tests
  // cover the cases where loose mocks are different from strict mocks
  function testSimpleExpectations() {
    mock.a(5);
    mock.b();
    mock.$replay();
    mock.a(5);
    mock.b();
    mock.$verify();

    mock.$reset();

    mock.a();
    mock.b();
    mock.$replay();
    mock.b();
    mock.a();
    mock.$verify();

    mock.$reset();

    mock.a(5).$times(2);
    mock.a(5);
    mock.a(2);
    mock.$replay();
    mock.a(5);
    mock.a(5);
    mock.a(5);
    mock.a(2);
    mock.$verify();
  }


  function testMultipleExpectations() {
    mock.a().$returns(1);
    mock.a().$returns(2);
    mock.$replay();
    assertEquals(1, mock.a());
    assertEquals(2, mock.a());
    mock.$verify();
  }


  function testMultipleExpectationArgs() {
    mock.a('asdf').$anyTimes();
    mock.a('qwer').$anyTimes();
    mock.b().$times(3);
    mock.$replay();
    mock.a('asdf');
    mock.b();
    mock.a('asdf');
    mock.a('qwer');
    mock.b();
    mock.a('qwer');
    mock.b();
    mock.$verify();

    mock.$reset();

    mock.a('asdf').$anyTimes();
    mock.a('qwer').$anyTimes();
    mock.$replay();
    mock.a('asdf');
    mock.a('qwer');
    goog.bind(mock.a, mock, 'asdf');
    goog.bind(mock.$verify, mock);
  }

  function testSameMethodOutOfOrder() {
    mock.a('foo').$returns(1);
    mock.a('bar').$returns(2);
    mock.$replay();
    assertEquals(2, mock.a('bar'));
    assertEquals(1, mock.a('foo'));
  }

  function testSameMethodDifferentReturnValues() {
    mock.a('foo').$returns(1).$times(2);
    mock.a('foo').$returns(3);
    mock.a('bar').$returns(2);
    mock.$replay();
    assertEquals(1, mock.a('foo'));
    assertEquals(2, mock.a('bar'));
    assertEquals(1, mock.a('foo'));
    assertEquals(3, mock.a('foo'));
    assertThrowsQuiet(function() {
      mock.a('foo');
      mock.$verify();
    });
  }

  function testSameMethodBrokenExpectations() {
    // This is a weird corner case.
    // No way to ever make this verify no matter what you call after replaying,
    // because the second expectation of mock.a('foo') will be masked by
    // the first expectation that can be called any number of times, and so we
    // can never satisfy that second expectation.
    mock.a('foo').$returns(1).$anyTimes();
    mock.a('bar').$returns(2);
    mock.a('foo').$returns(3);

    // LooseMock can detect this case and fail on $replay.
    assertThrowsQuiet(goog.bind(mock.$replay, mock));
    mock.$reset();

    // This is a variant of the corner case above, but it's harder to determine
    // that the expectation to mock.a('bar') can never be satisfied. So we don't
    // fail on $replay, but we do fail on $verify.
    mock.a(goog.testing.mockmatchers.isString).$returns(1).$anyTimes();
    mock.a('bar').$returns(2);
    mock.$replay();

    assertEquals(1, mock.a('foo'));
    assertEquals(1, mock.a('bar'));
    assertThrowsQuiet(goog.bind(mock.$verify, mock));
  }

  function testSameMethodMultipleAnyTimes() {
    mock.a('foo').$returns(1).$anyTimes();
    mock.a('foo').$returns(2).$anyTimes();
    mock.$replay();
    assertEquals(1, mock.a('foo'));
    assertEquals(1, mock.a('foo'));
    assertEquals(1, mock.a('foo'));
    // Note we'll never return 2 but that's ok.
    mock.$verify();
  }

  function testFailingFast() {
    mock.a().$anyTimes();
    mock.$replay();
    mock.a();
    mock.a();
    assertThrowsQuiet(goog.bind(mock.b, mock));
    mock.$reset();

    // too many
    mock.a();
    mock.b();
    mock.$replay();
    mock.a();
    mock.b();

    var message;
    silenceFailureLogging();
    try {
      mock.a();
    } catch (e) {
      message = e.message;
    }
    unsilenceFailureLogging();

    assertTrue('No exception thrown on unexpected call', goog.isDef(message));
    assertContains('Too many calls to a', message);
  }

  function testTimes() {
    mock.a().$times(3);
    mock.b().$times(2);
    mock.$replay();
    mock.a();
    mock.b();
    mock.b();
    mock.a();
    mock.a();
    mock.$verify();
  }


  function testFailingSlow() {
    // not enough
    mock.a().$times(3);
    mock.$replay();
    mock.a();
    mock.a();
    assertThrowsQuiet(goog.bind(mock.$verify, mock));

    mock.$reset();

    // not enough, interleaved order
    mock.a().$times(3);
    mock.b().$times(3);
    mock.$replay();
    mock.a();
    mock.b();
    mock.a();
    mock.b();
    assertThrowsQuiet(goog.bind(mock.$verify, mock));

    mock.$reset();
    // bad args
    mock.a('asdf').$anyTimes();
    mock.$replay();
    mock.a('asdf');
    assertThrowsQuiet(goog.bind(mock.a, mock, 'qwert'));
    assertThrowsQuiet(goog.bind(mock.$verify, mock));
  }


  function testArgsAndReturns() {
    mock.a('asdf').$atLeastOnce().$returns(5);
    mock.b('qwer').$times(2).$returns(3);
    mock.$replay();
    assertEquals(5, mock.a('asdf'));
    assertEquals(3, mock.b('qwer'));
    assertEquals(5, mock.a('asdf'));
    assertEquals(5, mock.a('asdf'));
    assertEquals(3, mock.b('qwer'));
    mock.$verify();
  }


  function testThrows() {
    mock.a().$throws('exception!');
    mock.$replay();
    assertThrowsQuiet(goog.bind(mock.a, mock));
    mock.$verify();
  }


  function testDoes() {
    mock.a(1, 2).$does(function(a, b) {return a + b;});
    mock.$replay();
    assertEquals('Mock should call the function', 3, mock.a(1, 2));
    mock.$verify();
  }

  function testIgnoresExtraCalls() {
    mock = new goog.testing.LooseMock(RealObject, true);
    mock.a();
    mock.$replay();
    mock.a();
    mock.b(); // doesn't throw
    mock.$verify();
  }

  function testSkipAnyTimes() {
    mock = new goog.testing.LooseMock(RealObject);
    mock.a(1).$anyTimes();
    mock.a(2).$anyTimes();
    mock.a(3).$anyTimes();
    mock.$replay();
    mock.a(1);
    mock.a(3);
    mock.$verify();
  }

  function testErrorMessageForBadArgs() {
    mock.a();
    mock.$anyTimes();

    mock.$replay();

    var message;
    silenceFailureLogging();
    try {
      mock.a('a');
    } catch (e) {
      message = e.message;
    }
    unsilenceFailureLogging();

    assertTrue('No exception thrown on verify', goog.isDef(message));
    assertContains('Bad arguments to a()', message);
  }
</script>
</body>
</html>
